-module(edrp_dispatcher).

-behaviour(gen_server).

%% API
-export([start/0,               start/1,
         start_link/0,          start_link/1,
         apply_server/1,        apply_server/2,
         return_server/2,       return_server/3,
         list_server/0,         list_server/1,
         add_server/1,          add_server/2,           add_server/3,
         add_serverList/1,      add_serverList/2,
         delete_server/1,       delete_server/2,
         delete_all_servers/0,  delete_all_servers/1,
         server_count/0,        server_count/1,
         get_loadFunc/0,        get_loadFunc/1,        
         set_loadFunc/1,        set_loadFunc/2,
         dump/0,                dump/1]).

%% gen_server callbacks
-export([init/1,
         handle_call/3,
         handle_cast/2,
         handle_info/2,
         terminate/2,
         code_change/3]).

-include("edrp_server.hrl").

-define(DEFAULT_NAME, ?MODULE).

-record(state, {loads    =  #loads{},
                loadFunc =  #loadFunc{}}).

%%==========================================================================
%% API
%%==========================================================================

%%--------------------------------------------------------------------------
%% Function: start() -> {ok, Pid} | ignore | {error, Error}
%% Description: Starts the dispatcher with default name. 
%%--------------------------------------------------------------------------
start() ->
    start(?DEFAULT_NAME).

%%--------------------------------------------------------------------------
%% Function: start(Name) -> {ok, Pid} | ignore | {error, Error}
%% Description: Starts the dispatcher with the Name, it starts by the
%%              gen_server_cluster. 
%%--------------------------------------------------------------------------
start(Name) ->
    gen_server_cluster:start(Name, ?MODULE, [], []).
    
%%--------------------------------------------------------------------------
%% Function: start_link() -> {ok, Pid} | ignore | {error, Error}
%% Description: Starts and link the dispatcher with default name. 
%%--------------------------------------------------------------------------
start_link() ->
    start_link(?DEFAULT_NAME).

%%--------------------------------------------------------------------------
%% Function: start_link(Name) -> {ok, Pid} | ignore | {error, Error}
%% Description: Starts and link the dispatcher with the Name, it starts by
%%              the gen_server_cluster. 
%%--------------------------------------------------------------------------
start_link(Name) ->
    gen_server_cluster:start_link(Name, ?MODULE, [], []).

%%--------------------------------------------------------------------------
%% Function: apply_server(Request) -> Addr | no_server
%% Description: Apply for a server from the default name dispatcher.
%%--------------------------------------------------------------------------
apply_server(Request) ->
    apply_server(?DEFAULT_NAME, Request).

%%--------------------------------------------------------------------------
%% Function: apply_server(Name, Request) -> Addr | no_server
%% Description: Apply for a server from the Name dispatcher.
%%--------------------------------------------------------------------------
apply_server(Name, Request) ->
    gen_server:call({global, Name}, {apply_server, Request}).

%%--------------------------------------------------------------------------
%% Function: return_server(Addr, Status) -> ok.
%% Description: Return the server back to the default name dispatcher with 
%%              the server status. Status is true means server alive. 
%%--------------------------------------------------------------------------
return_server(Addr, Status) when is_record(Addr, addr), 
                                 is_boolean(Status) ->
    return_server(?DEFAULT_NAME, Addr, Status).

%%--------------------------------------------------------------------------
%% Function: return_server(Name, Addr, Status) -> ok.
%% Description: Return the server back to the name dispatcher with the
%%              server status. Status is true means server alive.
%%--------------------------------------------------------------------------
return_server(Name, Addr, Status) when is_record(Addr, addr),
                                       is_boolean(Status) ->
    gen_server:cast({global, Name}, {return_server, Addr, Status}).

%%--------------------------------------------------------------------------
%% Function: list_server() -> ServerList.
%% Description: List all servers with their load and weight from the default
%%              name dispatcher.
%%--------------------------------------------------------------------------
list_server() ->
    list_server(?DEFAULT_NAME).

%%--------------------------------------------------------------------------
%% Function: list_server(Name) -> ServerList.
%% Description: List all servers with their load and weight from the default
%%              name dispatcher.
%%--------------------------------------------------------------------------
list_server(Name) ->
    gen_server:call({global, Name}, {list_server}).

%%--------------------------------------------------------------------------
%% Function: add_server(Addr) -> ok.
%% Description: Add a new server to the default name dispatcher with its ip 
%%              and port and default weight.
%%--------------------------------------------------------------------------
add_server(Addr) when is_record(Addr, addr)->
    add_server(?DEFAULT_NAME, Addr, ?DEFAULT_WEIGHT).

%%--------------------------------------------------------------------------
%% Function: add_server(Addr, Weight) -> ok.
%% Description: Add a new server to the default name dispatcher with its ip 
%%              and port and weight.
%%--------------------------------------------------------------------------
add_server(Addr, Weight) when is_record(Addr, addr) ->
    add_server(?DEFAULT_NAME, Addr, Weight).

%%--------------------------------------------------------------------------
%% Function: add_server(Name, Addr, Weight) -> ok.
%% Add a new server to the name  dispatcher with its ip and port and weight
%%--------------------------------------------------------------------------
add_server(Name, Addr, Weight) when is_record(Addr, addr)->
    gen_server:cast({global, Name}, {add_server, Addr, Weight}).

%%--------------------------------------------------------------------------
%% Function: add_serverList(ServerList) -> ok.
%% Description: Add servers in the server list to default name dispatcher.
%%--------------------------------------------------------------------------
add_serverList(ServerList) when is_list(ServerList) ->
    add_serverList(ServerList, ?DEFAULT_NAME).

%%--------------------------------------------------------------------------
%% Function: add_serverList(ServerList, Name) -> ok.
%% Description: Add servers in the server list to name dispatcher.
%%--------------------------------------------------------------------------
add_serverList(ServerList, Name) when is_list(ServerList) ->
    gen_server:cast({global, Name}, {add_server_list, ServerList}).

%%--------------------------------------------------------------------------
%% Function: delete_server(Addr) -> ok.
%% Description: Delete a server from the default name dispatcher.
%%--------------------------------------------------------------------------
delete_server(Addr) when is_record(Addr, addr) ->
    delete_server(?DEFAULT_NAME, Addr).

%%--------------------------------------------------------------------------
%% Function: delete_server(Name, Addr) -> ok.
%% Description: Delete a server from the name dispatcher.
%%--------------------------------------------------------------------------
delete_server(Name, Addr) when is_record(Addr, addr) ->
    gen_server:cast({global, Name}, {delete_server, Addr}).

%%--------------------------------------------------------------------------
%% Function: delete_all_servers() -> ok.
%% Description:  Delete all the servers from the default name dispatcher.
%%--------------------------------------------------------------------------
delete_all_servers() ->
    delete_all_servers(?DEFAULT_NAME).

%%--------------------------------------------------------------------------
%% Function: delete_all_servers(Name) -> ok.
%% Description:  Delete all the servers from the name dispatcher.
%%--------------------------------------------------------------------------
delete_all_servers(Name) ->
    gen_server:cast({global, Name}, {delete_all_servers}).

%%--------------------------------------------------------------------------
%% Function: server_count() -> Count.
%% Description: Get the count of servers from the default name dispatcher.
%%--------------------------------------------------------------------------
server_count() ->
    server_count(?DEFAULT_NAME).

%%--------------------------------------------------------------------------
%% Function: server_count(Name) -> Count.
%% Description: Get the count of servers from the name dispatcher.
%%--------------------------------------------------------------------------
server_count(Name) ->
    gen_server:call({global, Name}, {server_count}).    

%%--------------------------------------------------------------------------
%% Function: get_loadFunc() -> LoadFuncName.
%% Description: Get the load function name for the default name dispatcher
%%--------------------------------------------------------------------------
get_loadFunc() ->
    get_loadFunc(?DEFAULT_NAME).

%%--------------------------------------------------------------------------
%% Function: get_loadFunc(Name) -> LoadFuncName.
%% Description: Get the load function name for the default name dispatcher
%%--------------------------------------------------------------------------
get_loadFunc(Name) ->
    gen_server:call({global, Name}, {get_loadFunc}).

%%--------------------------------------------------------------------------
%% Function: set_loadFunc(LoadFunc) -> ok.
%% Description: Set the load function for the default name dispatcher
%%--------------------------------------------------------------------------
set_loadFunc(LoadFunc) when is_record(LoadFunc, loadFunc) ->
    set_loadFunc(?DEFAULT_NAME, LoadFunc).

%%--------------------------------------------------------------------------
%% Function: set_loadFunc(Name, LoadFunc) -> ok.
%% Description: Set the load function for the name dispatcher
%%--------------------------------------------------------------------------
set_loadFunc(Name, LoadFunc) when is_record(LoadFunc, loadFunc) ->
    gen_server:cast({global, Name}, {set_loadFunc, LoadFunc}).

%%--------------------------------------------------------------------------
%% Function: dump() -> ok.
%% Description: Dump the information of the default name dispatcher to tty.
%%--------------------------------------------------------------------------
dump() ->
    dump(?DEFAULT_NAME).

%%--------------------------------------------------------------------------
%% Function: dump(Name) -> ok.
%% Description: Dump the information of the name dispatcher to tty.
%%--------------------------------------------------------------------------
dump(Name) ->
    ServerCount = server_count(Name),
    io:format("All Servers: total ~p~n", [ServerCount]),
    ServerList = list_server(Name),
    F = fun(Server) ->
            Addr = Server#server.addr,
            io:format("~p\t~p\t~p\t~p~n", [Addr#addr.ip, Addr#addr.port, 
                                           Server#server.load,
                                           Server#server.weight])
        end,
    lists:foreach(F, ServerList),
    io:format("LoadFunction is ~p~n", [get_loadFunc(Name)]),
    ok.

%%==========================================================================
%% gen_server callbacks
%%==========================================================================

%%--------------------------------------------------------------------------
%% Function: init(Args) ->  {ok, State}          |
%%                          {ok, State, Timeout} |
%%                          ignore               |
%%                          {stop, Reason}       
%% Description: Initiates the server
%%--------------------------------------------------------------------------
init([]) ->
    case edrp_misc:read_servers() of
        {ok, ServerList, Length} ->
            Loads = #loads{serverList=ServerList, length=Length},
            {ok, #state{loads=Loads}};
        {error, Reason} ->
            {stop, {read_servers, Reason}}
    end.

%%--------------------------------------------------------------------------
%% Function: handle_call(Request, From, State) -> 
%%                          {reply, Reply, State}           |
%%                          {reply, Reply, State, Timout}   |
%%                          {noreply, State}                |
%%                          {noreply, State, Timeout}       |
%%                          {stop, Reason, Reply, State}    |
%%                          {stop, Reason, State}           
%% Description: Handling call messages
%%--------------------------------------------------------------------------
% Called by dispatcher to select an appropriate server from the server list
% with the load function in state, the new state is a update function.
handle_call({apply_server, Request}, _From, 
            #state{loads=Loads, loadFunc=LoadFunc}) ->
    case Loads#loads.length of
        % no servers in the server list
        0 ->
            Reply = no_server,
            StateUpdateFunc = fun(State) -> State end;
        _Length ->
            Func = LoadFunc#loadFunc.func,
            case catch Func(Loads, Request) of
                {Reply, UpdateLoadsFunc} ->
                    StateUpdateFunc = 
                    fun(State) ->
                        NewLoads = UpdateLoadsFunc(State#state.loads),
                        State#state{loads=NewLoads}
                    end;
                Error ->
                    error_logger:error_msg("load func - ~p~n", [Error]),
                    Reply = no_server,
                    StateUpdateFunc = fun(State) -> State end
            end
    end,
    {reply, Reply, StateUpdateFunc};

% Called by dispatcher to list all the server infomation in the server list,
% the new state is a update function which keeps state not change.    
handle_call({list_server}, _From, #state{loads=Loads}) ->
    Reply = Loads#loads.serverList, 
    StateUpdateFunc = fun(State) -> State end,
    {reply, Reply, StateUpdateFunc};

% Called by dispatcher to get the server count, the new state is a update
% function which keeps state not change.
handle_call({server_count}, _From, #state{loads=Loads}) ->
    Reply = Loads#loads.length,
    StateUpdateFunc = fun(State) -> State end,
    {reply, Reply, StateUpdateFunc};

% Called by dispatcher to get the load balance function, the new state is
% a update function.
handle_call({get_loadFunc}, _From,  #state{loadFunc=LoadFunc}) ->
    Reply = LoadFunc#loadFunc.name,
    StateUpdateFunc = fun(State) -> State end,
    {reply, Reply, StateUpdateFunc}.

%%--------------------------------------------------------------------------
%% Funtion: handle_cast(Msg, State) -> {noreply, State}         |
%%                                     {noreply, State, Timeout}|
%%                                     {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------------
% Called by dispatcher to return the server to the server list
handle_cast({return_server, Addr, Status}, State = #state{loads=Loads}) ->
    case Status of
        % the server is alive
        true ->
            List = Loads#loads.serverList,
            case lists:keysearch(Addr, 2, List) of  
                % the server is still in the server list
                {value, Server} ->
                    StateUpdateFunc = 
                    fun(S) ->
                        Loads = S#state.loads,
                        List = Loads#loads.serverList,
                        Load = Server#server.load,
                        NewServer = Server#server{load=Load-1},
                        NewServerList =
                        lists:keyreplace(Addr, 2, List, NewServer),
                        NewLoads = Loads#loads{serverList=NewServerList},
                        S#state{loads=NewLoads}
                    end;
                % the server is not in the server list
                false ->
                    StateUpdateFunc = fun(S) -> S end
            end,
            {noreply, StateUpdateFunc};    
        % the server is died, delete it
        false ->
            handle_cast({delete_server, Addr}, State)            
    end;

% Called by dispatcher to add a new server to the server list, the new state
% is a update function.
handle_cast({add_server, Addr, Weight}, #state{loads=Loads}) ->
    List = Loads#loads.serverList,
    StateUpdateFunc = 
    case lists:keymember(Addr, 2, List) of
        % the server is already in the server list
        true ->
            fun(State) -> State end;
        % the server is not in the server list
        false ->
            fun(State) ->
                Loads = State#state.loads,
                #loads{serverList=List, length=Length} = Loads,
                Server = #server{addr=Addr, load=0, weight=Weight},
                NewLoads = Loads#loads{serverList = [Server|List],
                                       length     = Length+1},
                State#state{loads=NewLoads}
            end
    end, 
    {noreply, StateUpdateFunc};
    
% Called by dispatcher to add the new servers to the server list, the new
% state is a update function.
handle_cast({add_server_list, ServerList}, _State) ->
    StateUpdateFunc = 
    fun(State = #state{loads=Loads}) ->
        #loads{serverList=List, length=Length} = Loads,
        F = fun(Server, {Count, L}) ->
                Addr = Server#server.addr,
                case lists:keysearch(Addr, 2, List) of
                    % the server is already in the server list
                    {value, Server} ->
                        {Count, L};
                    % the server is not in the server list
                    false ->
                        {Count+1, [Server|L]}
                end
            end,
        {AddCount, NewServerList} = lists:foldl(F, {0, List}, ServerList),
        NewLoads = Loads#loads{serverList=NewServerList,
                               length=Length+AddCount},
        State#state{loads=NewLoads}
    end,
    {noreply, StateUpdateFunc};

% Called by dispatcher to delete the server from the server list, the new 
% state is a update function.
handle_cast({delete_server, Addr}, #state{loads=Loads}) ->
    List = Loads#loads.serverList,
    StateUpdateFunc = 
    case lists:keymember(Addr, 2, List) of
        % the server is in the server list
        true ->
            fun(State) ->
                Loads = State#state.loads,
                #loads{serverList=List,length=Length,reqHash=Hash} = Loads, 
                NewLoads = 
                Loads#loads{serverList = lists:keydelete(Addr, 2, List),
                            length     = Length-1,
                            reqHash    = keyalldelete(Addr, 2, Hash)},
                State#state{loads=NewLoads}
            end;
        % the server is not in the server list
        false ->
            fun(State) -> State end
    end,
    error_logger:info_msg("delete server: ~p~n", [Addr]),
    {noreply, StateUpdateFunc};

% Called by dispatcher to delete all the server in the server list, the new
% state is a update function.
handle_cast({delete_all_servers}, _State) ->
    StateUpdateFunc = 
    fun(_S) ->
        #state{}
    end,
    {noreply, StateUpdateFunc};

% Called by dispatcher to set the load balance function, the new state is
% a update function.
handle_cast({set_loadFunc, LoadFunc}, _State) ->
    StateUpdateFunc = 
    fun(S) ->
        S#state{loadFunc=LoadFunc}
    end,
    {noreply, StateUpdateFunc}.

%%--------------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State}           |
%%                                       {noreply, State, Timeout}  |
%%                                       {stop, Reason, State}
%% Description: handling all non call/cast messages
%%--------------------------------------------------------------------------
handle_info(_Info, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%%              terminate. It should be the opposite of Module:init/1 and 
%%              do any necessary cleaning up. When it returns, the
%%              gen_server terminates with Reason. The return is ignored.
%%--------------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.

%%--------------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%==========================================================================
%% Internal functions
%%==========================================================================

%%--------------------------------------------------------------------------
%% Function: keyalldelete(Key, N, List) -> NewList.
%% Description: Return a copy of List where all the tuple whose Nth element
%%              compares equal to Key is deleted, if there is such tuple.
%%--------------------------------------------------------------------------
keyalldelete(Key, N, List) ->
    case lists:keydelete(Key, N, List) of
        List -> List;
        NewList -> keyalldelete(Key, N, NewList)
    end.
